
in vec3					v_ViewVector;
in vec3					v_LightVector;
in vec3					v_LightDirection;
in vec2					v_BumpTexCoord;
in vec2					v_DiffuseTexCoord;
in vec2					v_SpecularTexCoord;
in vec4					v_LightTexCoord;
in vec3					v_ShadowTexCoord;
in vec3					v_VertexColor;

uniform vec3				u_DiffuseColor;
uniform vec3				u_SpecularColor;
uniform vec2				u_SpecularParms;
uniform vec3				u_LightColor;
uniform vec2				u_DepthScaleAndBias;
uniform vec4				u_ShadowParms;
uniform vec4				u_JitterParms;

uniform sampler2D			u_BumpMap;
uniform sampler2D			u_DiffuseMap;
uniform sampler2D			u_SpecularMap;
uniform sampler2D			u_LightProjectionMap;
uniform sampler2D			u_LightFalloffMap;
uniform samplerCube			u_LightCubeMap;
uniform samplerCubeShadow		u_ShadowMap;
uniform sampler3D			u_JitterMap;


float ShadowFilter (){

	vec4	jitter;
	vec3	jitterTexCoord;
	vec3	shadowVecs[3];
	float	shadow, samples;
	float	receiver, depth;

	// Compute the direction vectors
	shadowVecs[0] = normalize(v_ShadowTexCoord);

	shadowVecs[1] = vec3(shadowVecs[0].z, -shadowVecs[0].x, shadowVecs[0].y);
	shadowVecs[1] -= shadowVecs[0] * dot(shadowVecs[0], shadowVecs[1]);
	shadowVecs[1] = normalize(shadowVecs[1]);

	shadowVecs[2] = cross(shadowVecs[1], shadowVecs[0]);

	// Compute distance to receiver
	receiver = max(abs(v_ShadowTexCoord.s), max(abs(v_ShadowTexCoord.t), abs(v_ShadowTexCoord.p)));

	// Compute shadowing
	jitterTexCoord = vec3(gl_FragCoord.xy * u_JitterParms.x, 0.0);

	if (u_ShadowParms.x == 1.0){
		jitter = texture(u_JitterMap, jitterTexCoord) * 2.0 - 1.0;

		// Compute biased depth and transform into screen space
		depth = receiver - length(jitter.xw * u_ShadowParms.zw);
		depth = 0.5 + 0.5 * ((depth * u_DepthScaleAndBias.x + u_DepthScaleAndBias.y) / depth);

		// Sample the shadow map with a modified texture coord
		shadow = texture(u_ShadowMap, vec4(shadowVecs[0] + (shadowVecs[1] * jitter.x * u_JitterParms.z) + (shadowVecs[2] * jitter.w * u_JitterParms.w), depth));
	}
	else {
		shadow = 0.0;

		for (samples = 0.0; samples < u_ShadowParms.x; samples += 2.0){
			jitter = texture(u_JitterMap, jitterTexCoord) * 2.0 - 1.0;

			jitterTexCoord.p += u_JitterParms.y;

			// Compute biased depth and transform into screen space
			depth = receiver - length(jitter.xy * u_ShadowParms.zw);
			depth = 0.5 + 0.5 * ((depth * u_DepthScaleAndBias.x + u_DepthScaleAndBias.y) / depth);

			// Sample the shadow map with a modified texture coord
			shadow += texture(u_ShadowMap, vec4(shadowVecs[0] + (shadowVecs[1] * jitter.x * u_JitterParms.z) + (shadowVecs[2] * jitter.y * u_JitterParms.w), depth));

			// Compute biased depth and transform into screen space
			depth = receiver - length(jitter.zw * u_ShadowParms.zw);
			depth = 0.5 + 0.5 * ((depth * u_DepthScaleAndBias.x + u_DepthScaleAndBias.y) / depth);

			// Sample the shadow map with a modified texture coord
			shadow += texture(u_ShadowMap, vec4(shadowVecs[0] + (shadowVecs[1] * jitter.z * u_JitterParms.z) + (shadowVecs[2] * jitter.w * u_JitterParms.w), depth));
		}

		shadow *= u_ShadowParms.y;
	}

	return shadow;
}

void main (){

	vec3	toView, toLight;
	vec3	localNormal;
	vec3	diffuse, specular, light;
	float	exponent, shadow;
	float	d;

	// Normalize the view vector
	toView = normalize(v_ViewVector);

	// Normalize the light vector
	toLight = normalize(v_LightVector);

	// Load the normal vector from the bump map
	localNormal = texture(u_BumpMap, v_BumpTexCoord).rgb * 2.0 - 1.0;
	localNormal = normalize(localNormal);

	// Modulate the diffuse map by the diffuse color
	diffuse = texture(u_DiffuseMap, v_DiffuseTexCoord).rgb * u_DiffuseColor;

	// Modulate the specular map by the specular color
	specular = texture(u_SpecularMap, v_SpecularTexCoord).rgb * u_SpecularColor;

	// Load the specular exponent from the specular map
	exponent = texture(u_SpecularMap, v_SpecularTexCoord).a;

	// Compute light attenuation
	light = textureProj(u_LightProjectionMap, v_LightTexCoord.stq).rgb * texture(u_LightFalloffMap, vec2(v_LightTexCoord.p, 0.5)).rgb * u_LightColor;

	// Modulate the light attenuation by the light cube map
	light *= texture(u_LightCubeMap, v_LightDirection).rgb;

	// Compute diffuse light contribution
	d = dot(toLight, localNormal);

	light *= max(d, 0.0);

	// Compute specular light contribution
	if (exponent == 0.0)
		specular *= u_SpecularParms.y;
	else {
		d = dot(toView, reflect(-toLight, localNormal));

		specular *= pow(max(d, 0.0), exponent * u_SpecularParms.x) * u_SpecularParms.y;
	}

	// Compute shadowing
	if (u_ShadowParms.x == 0.0)
		shadow = 1.0;
	else
		shadow = ShadowFilter();

	// Compute lighting and shadowing and modulate by the vertex color
	gl_FragColor.rgb = (diffuse + specular) * light * shadow * v_VertexColor;
}